﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MapData;
using System.IO;

namespace ImportData {
    
    /// <summary>
    /// An object which deals with importing data from
    /// .mid and .mif files
    /// </summary>
    public class ImportMIF : ImportMapDocument {

        private string currentLine;
        private List<string> filter;

        /// <summary>
        /// Constructor
        /// </summary>
        public ImportMIF() {
            this.currentLine = "";
            this.filter = new List<string>();
        }

        /// <summary>
        /// Adds a value to the filter list that will be used to determine what
        /// regions are added to the final map document
        /// </summary>
        /// <param name="value">Name of region you want to be included in the map document</param>        
        public void addFilter(string value) {
            filter.Add(value); 
       }

        /// <summary>
        /// Pre: A valid mif file needs to passed to the function
        /// Post: Outputs the result of the 'Append Method'
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public override MapDocument Load(string fileName) {
            return Append(fileName, new MapDocument());
        }


        /// <summary>
        /// Pre:A valid mif file needs to passed to the function
        /// Post: Loads the data from the mif file and creates a map document
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="doc"></param>
        /// <returns></returns>
        public override MapDocument Append(string fileName, MapDocument doc) {
            if (!File.Exists(fileName)) {
                throw new ImportException("File Does Not Exist");
            }
            
                /// Test to make sure the file is a .mif file.
            else if (!fileName.ToLower().EndsWith(".mif")){
                throw new ImportException("File is not a .mif file");
            }

            int lineCount = 0;
            StreamReader mifFile = File.OpenText(fileName);
            
            char[] delimiter = ",".ToCharArray(); //default;
            string[] columns = null;
            string[] types = null;
            int columnCount = 0;

            string input = null;
            while ((input = mifFile.ReadLine().Trim().ToLower()) != null) {
                
                if (input.StartsWith("data")) {
                    break;
                } else if (input.StartsWith("delimiter")) {
                    //TODO get delimiter for MID
                    delimiter = input.Substring(10).Trim("\" ".ToCharArray()).ToCharArray();
                } else if (input.StartsWith("columns")) {
                    if (int.TryParse(input.Substring(8).Trim(), out columnCount)) { 
                        columns = new string[columnCount];
                        types = new string[columnCount];
                        for (int i = 0; i < columnCount; i++) {
                            input = mifFile.ReadLine().Trim();
                            int pos = input.IndexOf(' ');
                            columns[i] = input.Substring(0, pos).Trim();
                            types[i] = input.Substring(pos).Trim();
                        }
                    }
                }
            }
           
            if (input == null) {
                mifFile.Close();
                return doc;
            } else {

                int mapObjectType = 0;
                while (!mifFile.EndOfStream) {
                    if (this.currentLine == null) {
                        currentLine = mifFile.ReadLine().Trim();
                    } 

                    lineCount++;

                    if ((mapObjectType = isMapObject(currentLine)) > 0) {
                        switch (mapObjectType) {
                            case 1: //Region
                                doc.addMapObject(extractRegion(currentLine, mifFile));
                                break;
                            case 2: //Point
                                doc.addMapObject(extractPoint(currentLine, mifFile));
                                break;
                            case 3: //Line
                                doc.addMapObject(extractLine(currentLine, mifFile));
                                break;
                            case 4: //Round Rect
                                doc.addMapObject(extractRoundRect(currentLine, mifFile));
                                break;
                            case 5: //Rect
                                doc.addMapObject(extractRect(currentLine, mifFile));
                                break;
                            case 6: //Text
                                doc.addMapObject(extractText(currentLine, mifFile));
                                break;
                            case 7: //PLine
                                doc.addMapObject(extractPLine(currentLine, mifFile));
                                break;                   
                        }
                        doc.getMapObjects()[doc.getMapObjects().Length - 1].Style = doc.getMapObjects().Length;
                    } else { 
                        currentLine = mifFile.ReadLine().Trim();
                    }                    
                }
            }

            mifFile.Close();

            //MID File
            if (File.Exists(fileName)) {


                string midFileName = fileName.Substring(0, fileName.Length - 3) + "mid";
                StreamReader midFile = File.OpenText(midFileName);
 
                int title = 1;
 


                //
                if (columns != null) {
                    if (types != null) {
                        foreach (MapObject o in doc.getMapObjects()) {
                            if (midFile.EndOfStream) {
                                break;
                            }
                            string[] midValues = midFile.ReadLine().Split(delimiter);
                            for (int i = 0; (i < columnCount) && (i < midValues.Length); i++) {
                                //Remove quotation marks
                                if (types[i].ToLower().StartsWith("char")) {
                                    midValues[i] = midValues[i].Trim("\" ".ToCharArray());
                                }

                                if (i == title) {
                                    o.Name = midValues[i];
                                } else {
                                    o.Description += "<b>" + columns[i] + " -</b> " + midValues[i] + "<br>";
                                }
                            }
                        }
                    }
                }
                midFile.Close();
                //string input = header.getHeaderValue("DELIMITER");            
                //char[] delimiter = input.Substring(1, input.Length-2).ToCharArray(); 
                //foreach (MifDataObject o in data) {                  
                //    if ((input = midFile.ReadLine()) == null) {                  
                //        break;                 
                //    }                 
                //    o.addMidData(input.Split(delimiter));            
                //}                        
                //midFile.Close();       
            }
            doc.removeItemsNotInList(filter.ToArray());
            return doc;

        }

        private MapObject extractRoundRect(string input, StreamReader mifFile) {
            string roundness = mifFile.ReadLine();
            return extractRect(input, mifFile);
        }

        private MapObject extractRect(string input, StreamReader mifFile) {
            //﻿RECT x1 y1 x2 y2 	 
            //[ PEN (width, pattern, color)] 	 
            //[ BRUSH (pattern, forecolor, backcolor)] 	 
            //get style info
            SimplePolygon aPoly = new SimplePolygon();
            string[] values = input.Substring(4).Trim().Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            if (values.Length < 4) {
                throw new ImportException("Invalid Rectangle");
            }
            SimplePoint p1 = new SimplePoint();            
            SimplePoint p2 = new SimplePoint();
            SimplePoint p3 = new SimplePoint();
            SimplePoint p4 = new SimplePoint();
            if (!p1.tryParse(values[0] + " " + values[1], " ")) {
                throw new ImportException("Invalid Coordinate in Rectangle");
            }
            if (!p3.tryParse(values[2] + " " + values[4]," ")) {
                throw new ImportException("Invalid Coordinate in Rectangle");
            }
            p2.X = p1.X;
            p2.Y = p3.Y;
            p4.X = p3.X;
            p4.Y = p1.Y;
            aPoly.addPoint(p1);
            aPoly.addPoint(p2);
            aPoly.addPoint(p3);
            aPoly.addPoint(p4);

            Polygon returnValue = new Polygon();
            returnValue.addPolygon(aPoly);
            //Get the style info
            while (isStyle(mifFile)) {
                this.currentLine = this.currentLine.Trim().ToLower();
                if (currentLine.StartsWith("pen")) {
                    string[] stylevalues = currentLine.Substring(4).Trim("() ".ToCharArray()).Split(",".ToCharArray());
                    if (values.Length > 2) {
                        returnValue.LineColor = mifColorToWeb(stylevalues[2]);
                        int avalue = 0;
                        if (!int.TryParse(values[0], out avalue)) {
                            throw new ImportException("Invalid Line Width");
                        }
                        returnValue.LineWidth = avalue;
                    }
                    //Pen (2,2,16711680)
                } else if (currentLine.StartsWith("brush")) {
                    string[] stylevalues = currentLine.Substring(6).Trim("() ".ToCharArray()).Split(",".ToCharArray());
                    //BRUSH (pattern, forecolor, backcolor)
                    if (values.Length > 2) {
                        returnValue.FillColor = mifColorToWeb(stylevalues[2]);
                    }
                    //Brush (1,0,16777215)
                } 
            }
            return returnValue; 
        }

        private MapObject extractText(string input, StreamReader mifFile) {
            /*
             TEXT ”textstring”
            x1 y1 x2 y2
            [ FONT...]
            [ Spacing {1.0 | 1.5 | 2.0}]
            [ Justify {Left | Center | Right}]
            [ Angle text_angle]
            [ Label Line {simple | arrow} x y ]
             */
            string name;
            if (input.Length>4) {
                name = input.Substring(4).Trim("\" ".ToCharArray());
            } else {
                name = mifFile.ReadLine().Trim("\" ".ToCharArray());
            }
            //get point
            string[] values = mifFile.ReadLine().Trim().Split(" ".ToCharArray());
            SimplePoint p1 = new SimplePoint();
            SimplePoint p2 = new SimplePoint();
            SimplePoint p3 = new SimplePoint();
            if (!p1.tryParse(values[0] + " " + values[1], " ")) {
                throw new ImportException("Invalid Coordinate in Text");
            }
            if (!p2.tryParse(values[2] + " " + values[3], " ")) {
                throw new ImportException("Invalid Coordinate in Text");
            }
            p3.X = p1.X + ((p1.X - p2.X) / 2);
            p3.Y = p1.Y + ((p1.Y - p2.Y) / 2);
            Point returnValue = new Point(p3);
            //get style info
            long pos = mifFile.BaseStream.Position;
            input = mifFile.ReadLine().Trim().ToLower();

            if (input.StartsWith("font")) {
                //TODO font etc for text
            } else if (input.StartsWith("spacing")) {
            } else if (input.StartsWith("justify")) {
            } else if (input.StartsWith("angle")) {
            } else if (input.StartsWith("label")) {                
            } else {
                mifFile.BaseStream.Position = pos;
            }
            return returnValue;
        }

        private MapObject extractPLine(string input, StreamReader mifFile) {
            input = input.ToLower();
            int numberOfLines = 1;
            int numberOfCoords = 0;
            
            Polygon returnValue = new Polygon();
            returnValue.FillColor = ""; //Fill set to nothing, this is how we know it's lines

            if (input.Contains("multiple")) {
                if (!int.TryParse(input.Substring(input.LastIndexOf(' ')), out numberOfLines)) {
                    throw new Exception("Invalid PolyLine");
                }
            } else {
                if (!(int.TryParse(input.Substring(5).Trim(), out numberOfCoords))) {
                    throw new Exception("Invalid PolyLine");
                }
            }
            for (int lines = 0; lines < numberOfLines; lines++) {
                if (numberOfLines > 1) {                    
                    if (!int.TryParse(mifFile.ReadLine(), out numberOfCoords)) {
                        throw new Exception("Invalid PolyLine");
                    }
                }
                SimplePolygon aPLine = new SimplePolygon();
                for (int coords = 0; coords < numberOfCoords; coords++) {
                    SimplePoint tmpPoint = new SimplePoint();
                    if (tmpPoint.tryParse(mifFile.ReadLine(), " ")) {
                        aPLine.addPoint(tmpPoint);
                    }
                }
                returnValue.addPolygon(aPLine);
            }
            //Get the style info
            while (isStyle(mifFile)) {
                this.currentLine = this.currentLine.Trim().ToLower();
                if (currentLine.StartsWith("pen")) {
                    string[] values = currentLine.Substring(4).Trim("() ".ToCharArray()).Split(",".ToCharArray());
                    if (values.Length > 2) {
                        returnValue.LineColor = mifColorToWeb(values[2]);
                        int avalue = 0;
                        if (!int.TryParse(values[0], out avalue)) {
                            throw new ImportException("Invalid Line Width");
                        }
                        returnValue.LineWidth = avalue;
                    }
                    //Pen (2,2,16711680)             
                } else if (currentLine.StartsWith("smooth")) {
                    //TODO smooth polylines
                }
            }


            return returnValue;            
        }

        private Line extractLine(string input, StreamReader mifFile) {
            //LINE x1 y1 x2 y2
            //[ PEN (width, pattern, color)]
            string[] values = input.Substring(4).Trim().Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            SimplePoint firstPoint = new SimplePoint();
            if (values.Length<4) {
                throw new ImportException("Cannot Convert Line - Invalid Value");
            }
            if (!firstPoint.tryParse(values[0]+" "+values[1], " ")) {
                throw new ImportException("Invalid Coordinate in Line");
            }
            SimplePoint secondPoint = new SimplePoint();
            if (!secondPoint.tryParse(values[2]+" "+values[3], " ")) {
                throw new ImportException("Invalid Coordinate in Line");
            }
            Line returnValue = new Line();
            returnValue.addPoint(firstPoint);
            returnValue.addPoint(secondPoint);
            //Get the style info
            while (isStyle(mifFile)) {
                this.currentLine = this.currentLine.Trim().ToLower();
                if (currentLine.StartsWith("pen")) {
                    string[] styvalues = currentLine.Substring(4).Trim("() ".ToCharArray()).Split(",".ToCharArray());
                    if (values.Length > 2) {
                        returnValue.LineColor = mifColorToWeb(styvalues[2]);
                        int avalue = 0;
                        if (!int.TryParse(styvalues[0], out avalue)) {
                            throw new ImportException("Invalid Line Width");
                        }
                        returnValue.LineWidth = avalue;
                    }
                } 
            }
            return returnValue;
        }

        private Point extractPoint(string input, StreamReader mifFile) {

            SimplePoint value = new SimplePoint();
            if (!value.tryParse(input.Substring(5).Trim(), " ")) { 
                throw new ImportException("Invalid Coordinate in Point");
            }
            Point returnValue = new Point(value);

            //get style info
            //Get the style info
            while (isStyle(mifFile)) {
                this.currentLine = this.currentLine.Trim().ToLower();
                if (currentLine.StartsWith("symbol")) { //[ SYMBOL (shape, color, size)]
                    string[] values = currentLine.Substring(4).Trim("() ".ToCharArray()).Split(",".ToCharArray());
                    if (values.Length > 2) {
                        returnValue.IconColor = mifColorToWeb(values[1]);
                        int avalue = 0;
                        if (!int.TryParse(values[0], out avalue)) {
                            throw new ImportException("Invalid Symbol Shape");
                        }
                        if (avalue == 31) { //blank icon
                            returnValue.IconType = "";
                        } else if (avalue<47) {
                            switch(avalue%6) {
                                case 0:
                                    returnValue.IconType = "http://maps.google.com/mapfiles/kml/shapes/triangle.png";
                                    break;
                                case 1:
                                    returnValue.IconType = "http://maps.google.com/mapfiles/kml/paddle/wht-blank.png";
                                    break;
                                case 2:
                                    returnValue.IconType = "http://maps.google.com/mapfiles/kml/shapes/square.png";
                                    break;
                                case 3:
                                    returnValue.IconType = "http://maps.google.com/mapfiles/kml/shapes/open-diamond.png";
                                    break;
                                case 4:
                                    returnValue.IconType = "http://maps.google.com/mapfiles/kml/shapes/target.png";
                                    break;
                                case 5:
                                    returnValue.IconType = "http://maps.google.com/mapfiles/kml/shapes/star.png";
                                    break;
                            }
                        } else {
                            returnValue.IconType = "http://maps.google.com/mapfiles/kml/paddle/wht-blank.png";
                        }
                        if (!int.TryParse(values[2], out avalue)) {
                            throw new ImportException("Invalid Symbol Size");
                        }
                        returnValue.IconScale = (float)(avalue/10.0);
                    }
                } 
            }

            return returnValue;
           
        }

        private int isMapObject(string value) {
            value.Trim();
            value = value.ToLower();
            if (value.StartsWith("region")) {                
                return 1;
            } else if (value.StartsWith("point")) {
                return 2;
            } else if (value.StartsWith("line")) {
                return 3;
            } else if (value.StartsWith("rountrect")) {
                return 4;
            } else if (value.StartsWith("rect")) {
                return 5;
            } else if (value.StartsWith("text")) {
                return 6;
            } else if (value.StartsWith("pline")) {
                return 7;
            }
            return 0;
        }

        private Polygon extractRegion(string input, StreamReader mifFile) {
            int numberOfRegions;
            Polygon region = new Polygon();
            if (input.Length > 6) {
                input = input.Substring(6).Trim();
            }
            //Find out how many polygons there are
            if (!int.TryParse(input, out numberOfRegions)) {
                numberOfRegions = 1;
            }
            //Get the points for each polygon
            for (int i = 0; i < numberOfRegions; i++) {
                int numberPoints = 0;
                if (int.TryParse(mifFile.ReadLine(), out numberPoints)) {
                    //get the next x amount of lines
                    SimplePolygon aPoly = new SimplePolygon();
                    while (numberPoints > 0) {
                        SimplePoint aPoint = new SimplePoint();
                        if (aPoint.tryParse(mifFile.ReadLine(), " ")) {
                            aPoly.addPoint(aPoint);
                        } else {
                            //Exception
                            throw new ImportException("Invalid Coordinate in Region");
                        }
                        numberPoints--;
                    }
                    bool isInner = false;
                    foreach (SimplePolygon outPoly in region.getPolygons()) {
                        if (outPoly.contains(aPoly)) {
                            isInner = true;
                            break;
                        }   
                    }
                    if (!isInner) {
                        region.addPolygon(aPoly);
                    }
                } else {
                    //Exception, invalid number of points
                    throw new ImportException("Invalid Number of Points");
                }
            }
            //Get the style info
            while (isStyle(mifFile)) {
                this.currentLine = this.currentLine.Trim().ToLower();
                if (currentLine.StartsWith("pen")) {
                    string[] values = currentLine.Substring(4).Trim("() ".ToCharArray()).Split(",".ToCharArray());
                    if (values.Length>2) {
                        region.LineColor = mifColorToWeb(values[2]);
                        int avalue = 0;
                        if (!int.TryParse(values[0], out avalue)) {
                            throw new ImportException("Invalid Line Width");
                        }
                        region.LineWidth = avalue;
                    }
                    //Pen (2,2,16711680)
                } else if (currentLine.StartsWith("brush")) {
                    string[] values = currentLine.Substring(6).Trim("() ".ToCharArray()).Split(",".ToCharArray());
                    //BRUSH (pattern, forecolor, backcolor)
                    if (values.Length > 2) {
                        region.FillColor = mifColorToWeb(values[2]);
                    }
                    //Brush (1,0,16777215)
                } else if (currentLine.StartsWith("center")) {
                    //TODO centre of region
                } 
            }
            return region;
        }

        private bool isStyle(StreamReader mifFile) {
            if (!mifFile.EndOfStream) {
                this.currentLine = mifFile.ReadLine();
                if (isMapObject(currentLine.Trim())>0) {
                    return false;
                }
                return true;
            }
            return false; ;
            
        }
        private string mifColorToWeb(string value) {
              //(red * 65536) + (green * 256) + blue
            //AABBGGRR Alpha, Blue, Green, Red
            int red, green, blue;
            int color = int.Parse(value);
            blue = color % 256;
            green = (((color-blue)/256)%256);
            red = (((color - blue) - (green * 256)) / 65536);
            return "99" + blue.ToString("X").PadLeft(2, '0') + green.ToString("X").PadLeft(2, '0') + red.ToString("X").PadLeft(2, '0');

        }
    }
}
